/******************************************************************************
 *  Copyright 2013 Bernhard Grünewaldt                                        *
 *                                                                            *
 *  Licensed under the Apache License, Version 2.0 (the "License");           *
 *  you may not use this file except in compliance with the License.          *
 *  You may obtain a copy of the License at                                   *
 *                                                                            *
 *      http://www.apache.org/licenses/LICENSE-2.0                            *
 *                                                                            *
 *  Unless required by applicable law or agreed to in writing, software       *
 *  distributed under the License is distributed on an "AS IS" BASIS,         *
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  *
 *  See the License for the specific language governing permissions and       *
 *  limitations under the License.                                            *
 ******************************************************************************/
package com.google.code.greenwood.confluencemavenreleaseinfomacro.macro;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.httpclient.HttpException;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;

import com.atlassian.cache.Cache;
import com.atlassian.cache.CacheManager;
import com.atlassian.confluence.content.render.xhtml.ConversionContext;
import com.atlassian.confluence.macro.Macro;
import com.atlassian.confluence.macro.MacroExecutionException;
import com.atlassian.confluence.setup.BootstrapManager;
import com.atlassian.confluence.util.GeneralUtil;
import com.atlassian.renderer.RenderContext;
import com.atlassian.renderer.v2.RenderMode;
import com.atlassian.renderer.v2.macro.BaseMacro;
import com.atlassian.renderer.v2.macro.MacroException;
import com.google.code.greenwood.confluencemavenreleaseinfomacro.cache.ReleaseInformationCache;
import com.google.code.greenwood.confluencemavenreleaseinfomacro.parser.DomDocumentToReleaseInfoParser;
import com.google.code.greenwood.confluencemavenreleaseinfomacro.parser.HttpReleaseInfoDownloader;
import com.google.code.greenwood.confluencemavenreleaseinfomacro.pojo.ReleaseInformation;
import com.google.code.greenwood.confluencemavenreleaseinfomacro.pojo.StaticClassWrapper;
import com.google.code.greenwood.confluencemavenreleaseinfomacro.transformer.HtmlTransformer;


public class ReleaseInfoMacro extends BaseMacro implements Macro {
	/* Logging */
	private static final Logger log = LoggerFactory.getLogger(ReleaseInfoMacro.class);

	/* Dependencies */
	private StaticClassWrapper staticClassWrapper;
	private HtmlTransformer htmlTransformer;
	private CacheManager cacheManager;
    private BootstrapManager bootstrapManager;

   
	/* CACHE */
	public final static String RELEASEINFO_MACRO_CACHE_KEY = "confluencemavenreleaseinfomacro";
	public final static String MACRO_ACTION_PREFIX = "confluencemavenreleaseinfomacro";

	private long cacheTime = 15*60; // cache expires after 15 mins

	/* Macro Params */
	public final static String urlToReleasePomXmlKey = "urlToReleasePomXml";	
	public final static String baseUrlToMavenRepoKey = "baseUrlToMavenRepo";
	public final static String httpUsernameKey = "httpUsername";	
	public final static String httpPasswordKey = "httpPassword";
	
	/* Constructor for Dependency Injection */
	// https://developer.atlassian.com/display/CONFDEV/Accessing+Confluence+Components+from+Plugin+Modules
	public ReleaseInfoMacro(HtmlTransformer htmlTransformer, StaticClassWrapper staticClassWrapper, CacheManager cacheManager, BootstrapManager bootstrapManager) {
		log.info("constuctor for di called");
		this.staticClassWrapper = staticClassWrapper;
		this.htmlTransformer = htmlTransformer;
		this.cacheManager = cacheManager;
		this.bootstrapManager = bootstrapManager;
	}
	
	@Override
	public String execute(Map<String, String> params, String body, ConversionContext conversionContext) throws MacroExecutionException {
		String httpUsername = null;
		String httpPassword = null;
		String baseUrlToMavenRepo = null;
		String urlToReleasePomXml = null;
		
		
		if (params.containsKey(urlToReleasePomXmlKey)) {
        	urlToReleasePomXml = params.get(urlToReleasePomXmlKey);
        }
        if (params.containsKey(baseUrlToMavenRepoKey)) {
        	baseUrlToMavenRepo = params.get(baseUrlToMavenRepoKey);
        }
        if (params.containsKey(httpUsernameKey)) {
        	httpUsername = params.get(httpUsernameKey);
        }
        if (params.containsKey(httpPasswordKey)) {
        	httpPassword = params.get(httpPasswordKey);
        }
        
		String innerKeyCacheName = ReleaseInformationCache.generateCacheKey(urlToReleasePomXml, baseUrlToMavenRepo, httpUsername, httpPassword);

        if (urlToReleasePomXml != null) {
			try {
				
				ReleaseInformation info = getFromCache(innerKeyCacheName);
				if (info == null) {
					// (re)create cache
					HttpReleaseInfoDownloader down = new HttpReleaseInfoDownloader();
					File parentpom = down.downloadMavenReleaseRecursively(new URL(urlToReleasePomXml), httpUsername,httpPassword);
					
					DomDocumentToReleaseInfoParser parser = new DomDocumentToReleaseInfoParser();
					info = parser.parsePomFilesRecursive(parentpom, true);
					info.setUrlToReleasePomXml(urlToReleasePomXml);
					info.setBaseUrlToMavenRepo(baseUrlToMavenRepo);
					// cleanup 
					FileUtils.deleteDirectory(parentpom.getParentFile());
					
					putToCache(info, innerKeyCacheName);
				}
				Map<String,Object> additionalVelocityParams = new HashMap<String,Object>();
				
				final String refreshActionPath = bootstrapManager.getWebAppContextPath() + "/"+MACRO_ACTION_PREFIX+"/actions/refreshcache.action?pageId=" + GeneralUtil.urlEncode(conversionContext.getEntity().getIdAsString())+"&cachekey="+GeneralUtil.urlEncode(innerKeyCacheName);
				additionalVelocityParams.put("refreshActionPath", refreshActionPath);
				
				return htmlTransformer.toHtml(info, HtmlTransformer.Macroselector.INFOMACRO, additionalVelocityParams);
	
			} catch (SAXException e) {
				log.debug("SAXException: ",e);
				return htmlTransformer.renderErrorWithTemplate(staticClassWrapper.i18n_getText("releaseinfomacro.exceptiontext.saxexception"), HtmlTransformer.Macroselector.INFOMACRO, null);
			} catch (MalformedURLException e) {
				log.debug("MalformedURLException: ",e);
				return htmlTransformer.renderErrorWithTemplate(staticClassWrapper.i18n_getText("releaseinfomacro.exceptiontext.malformedurlexception"), HtmlTransformer.Macroselector.INFOMACRO, null);
			} catch (HttpException e) {
				log.debug("HttpException: ",e);
				return htmlTransformer.renderErrorWithTemplate(staticClassWrapper.i18n_getText("releaseinfomacro.exceptiontext.httpexception", new String[]{e.getMessage()}), HtmlTransformer.Macroselector.INFOMACRO, null);
			} catch (IOException e) {
				log.debug("IOException: ",e);
				return htmlTransformer.renderErrorWithTemplate(staticClassWrapper.i18n_getText("releaseinfomacro.exceptiontext.ioexception", new String[]{e.getMessage()}), HtmlTransformer.Macroselector.INFOMACRO, null);
			} catch(Exception e) {
				log.error("Unexpected Exception: ",e);
				return htmlTransformer.renderErrorWithTemplate(staticClassWrapper.i18n_getText("releaseinfomacro.exceptiontext.exception", new String[]{e.getMessage()}), HtmlTransformer.Macroselector.INFOMACRO, null);
			}
        }		
		return htmlTransformer.renderErrorWithTemplate(staticClassWrapper.i18n_getText("releaseinfomacro.norurl.specified"), HtmlTransformer.Macroselector.INFOMACRO, null);
	}

	private ReleaseInformation getFromCache(String innerKeyCacheName) {
		if (innerKeyCacheName != null && ! innerKeyCacheName.isEmpty()) {
			Cache cache = cacheManager.getCache(RELEASEINFO_MACRO_CACHE_KEY);		
	
			ReleaseInformationCache cacheObj = null;
			try {
				cacheObj = (ReleaseInformationCache) cache.get(innerKeyCacheName);
			} catch (Exception e) {
				// If ReleaseInfoMacroCacheObject of old version with different footprint is already there, discard the object.
				log.info("Cache gets renewed because of ClassCastException."+e.getMessage());
				cacheObj = null;
			}
			if (cacheObj != null) {
				return cacheObj.getReleaseInformation();
			} else {
				log.debug("cache is null!");
			}
			log.debug("Recreating cache.");
		}
		return null;
	}
	
	private void putToCache(ReleaseInformation releaseInformation, String innerKeyCacheName) {
		if (innerKeyCacheName != null && ! innerKeyCacheName.isEmpty()) {
			Cache cache = cacheManager.getCache(RELEASEINFO_MACRO_CACHE_KEY);		
			
			Date lastCacheUpdate = staticClassWrapper.newDate();
			releaseInformation.setLastCacheUpdate(lastCacheUpdate);
			ReleaseInformationCache rimcache = new ReleaseInformationCache();
			rimcache.setReleaseInformation(releaseInformation);
			rimcache.setCreated(lastCacheUpdate);
			
			cache.put(innerKeyCacheName, rimcache);
		}
	}
	
	@Override
	public BodyType getBodyType() {
		return BodyType.NONE;
	}


	@Override
	public OutputType getOutputType() {
		return OutputType.INLINE;
	}
	
	@SuppressWarnings("unchecked")
	@Override
    public String execute(@SuppressWarnings("rawtypes") Map params, String body, RenderContext renderContext) throws MacroException
    {
        try
        {
            return execute(params, body, (ConversionContext) null);
        }
        catch(MacroExecutionException e)
        {
            throw new MacroException(e);
        }
    }


	@Override
	public RenderMode getBodyRenderMode() {
		return RenderMode.INLINE;
	}


	@Override
	public boolean hasBody() {
		return false;
	}



	public void setCacheManager(CacheManager cacheManager) {
		this.cacheManager = cacheManager;
	}


	public void setCacheTime(long cacheTime) {
		this.cacheTime = cacheTime;
	}


	public void setStaticClassWrapper(StaticClassWrapper staticClassWrapper) {
		this.staticClassWrapper = staticClassWrapper;
	}


	public void setHtmlTransformer(HtmlTransformer htmlTransformer) {
		this.htmlTransformer = htmlTransformer;
	}



    
}
